﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  JoinNowVoiceService.cs
//    
// Abstract:
//
// Workflow service class for performing the asynchronous operations for Join Now
// via voice and then notifying the appropriate activities when the operation
// is complete.
//  
//--

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.OfficeCommunicationsServer.Applications.Common;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.AudioVideo;
using Microsoft.Rtc.Collaboration.Conferencing;
using Microsoft.Rtc.Signaling;

namespace Microsoft.EnhancedReminders.Dispatcher.Workflow
{
    /// <summary>
    /// Workflow service class for performing the asynchronous operations for Join Now
    /// via voice and then notifying the appropriate activities when the operation
    /// is complete.
    /// </summary>
    public class JoinNowVoiceService : JoinNowAbstractService
	{
        private Dictionary<string, VoiceConferencingInformation> _outstandingInvites;

        /// <summary>
        /// ctor.
        /// </summary>
        public JoinNowVoiceService() : base()
        {
            _outstandingInvites = new Dictionary<string, VoiceConferencingInformation>();
        }

        /// <summary>
        /// Begins the asynchronous operations necessary to transfer the call to a conference.
        /// </summary>
        protected override void JoinConference(Guid jobId, Call erCall, CalendarNotificationItem notification)
        {
            AudioVideoCall avCall = erCall as AudioVideoCall;
            Debug.Assert(avCall != null, "avCall != null");
            Debug.Assert(notification != null, "notification != null");
            Debug.Assert(notification.ReminderItem != null, "notification.ReminderItem != null");
            Debug.Assert(!String.IsNullOrEmpty(notification.ReminderItem.ConferenceAddress), "conferenceAddress");

            Conversation conversation = new Conversation(avCall.Conversation.Endpoint);
            ConferenceSession conferenceSession = conversation.ConferenceSession;
            AudioVideoMcuSession mcu = conversation.ConferenceSession.AudioVideoMcuSession;
            ConferenceJoinInformation info = new ConferenceJoinInformation(new RealTimeAddress(notification.ReminderItem.ConferenceAddress));

            mcu.StateChanged += McuStateChanged;

            VoiceConferencingInformation joinNowInfo = new VoiceConferencingInformation(notification.Context, avCall, mcu, conversation, conferenceSession, jobId);

            // we need to store the join now information so that we can look it up when the MCU state changes to active.
            _outstandingInvites.Add(conversation.Id, joinNowInfo);

            Logger.WriteTraceInformation(notification.Context, "Attempting to join conference.");
            conferenceSession.BeginJoin(info, JoinCompleted, joinNowInfo);
        }

        /// <summary>
        /// MCU state change event handler.
        /// </summary>
        private void McuStateChanged(object sender, StateChangedEventArgs<McuSessionState> e)
        {
            Logger.WriteTraceInformation("MCU state changed to {0}", e.State);

            if (e.State == McuSessionState.Active)
            {
                AudioVideoMcuSession mcu = sender as AudioVideoMcuSession;

                Debug.Assert(mcu != null, "mcu != null");
                //mcu.Conference.Conversation.Id
                Debug.Assert(_outstandingInvites.ContainsKey(mcu.ConferenceSession.Conversation.Id), "_outstandingInvites.ContainsKey(mcu.Conference.Conversation.Id)");

                VoiceConferencingInformation information = _outstandingInvites[mcu.ConferenceSession.Conversation.Id];
                _outstandingInvites.Remove(mcu.ConferenceSession.Conversation.Id);

                try
                {
                    Logger.WriteTraceInformation(information._context, "Now sending invitation.");                   
                   
                    mcu.BeginTransfer(information._call, new McuTransferOptions(), TransferSupervisedCompleted, information);
                }
                catch (InvalidOperationException ex)
                {
                    Logger.WriteTraceError(information._context, ex, "Failed to join conference due to unexpected error.");
                    NotifyWorkflowOfCompletedEvent(information._jobId);
                }
            }
        }

        /// <summary>
        /// Async callback for when the bot has joined the conference. Once the bot is in the
        /// conference it can invite the remote party to the conference.
        /// </summary>
        private void JoinCompleted(IAsyncResult asyncResult)
        {
            Debug.Assert(asyncResult != null, "asyncResult != null");
            VoiceConferencingInformation joinNowInfo = asyncResult.AsyncState as VoiceConferencingInformation;

            try
            {
                Debug.Assert(joinNowInfo != null, "joinNowInfo != null");

                joinNowInfo._session.EndJoin(asyncResult);
                Logger.WriteTraceInformation(joinNowInfo._context, "Bot account has joined the conference.");
            }
            catch (Exception e)
            {
                Logger.WriteTraceError(joinNowInfo._context, e, "Failed to join conference due to unexpected error.");
                Helpers.FilterIrrecoverableExceptions(e);
                NotifyWorkflowOfCompletedEvent(joinNowInfo._jobId);
            }
        }

        /// <summary>
        /// Async callback for when the supervised transfer to the conference has completed.
        /// </summary>
        private void TransferSupervisedCompleted(IAsyncResult asyncResult)
        {
            Debug.Assert(asyncResult != null, "asyncResult != null");
            VoiceConferencingInformation joinNowInfo = asyncResult.AsyncState as VoiceConferencingInformation;

            try
            {
                Debug.Assert(joinNowInfo != null, "joinNowInfo != null");

                joinNowInfo._mcu.EndTransfer(asyncResult);
                Logger.WriteTraceInformation(joinNowInfo._context, "Call has been transfered.");
                joinNowInfo._conversation.BeginTerminate(TerminateCompleted, joinNowInfo);
            }
            catch (Exception e)
            {
                Logger.WriteTraceError(joinNowInfo._context, e, "Failed to join conference due to unexpected error.");
                Helpers.FilterIrrecoverableExceptions(e);
                NotifyWorkflowOfCompletedEvent(joinNowInfo._jobId);
            }
        }

        /// <summary>
        /// Async callback for when the bot account has left the conference. Sets the
        /// wait event.
        /// </summary>
        private void TerminateCompleted(IAsyncResult asyncResult)
        {
            Debug.Assert(asyncResult != null, "asyncResult != null");
            VoiceConferencingInformation joinNowInfo = asyncResult.AsyncState as VoiceConferencingInformation;

            try
            {
                Debug.Assert(joinNowInfo != null, "joinNowInfo != null");
                joinNowInfo._conversation.EndTerminate(asyncResult);
                Logger.WriteTraceInformation(joinNowInfo._context, "Bot has left the conference.");
            }
            catch (Exception e)
            {
                Logger.WriteTraceError(joinNowInfo._context, e, "Exception occured when attempting to leave conference.");
                Helpers.FilterIrrecoverableExceptions(e);
            }
            finally
            {
                Logger.WriteTraceInformation(joinNowInfo._context, "Join now complete.");
                NotifyWorkflowOfCompletedEvent(joinNowInfo._jobId);
            }
        }
	}
}
